#!/usr/bin/env python
import signal
#VERSION 0.01 (2011-09-13 di 05:54 pm) basic functionality, first option parsing.
#VERSION 0.02 (2011-09-15 do 08:13 am) sendpfast was giving me a hard time
#VERSION 0.03 (2011-09-18 zo 10:21 am) finally figured out how to inject the synjacks
#VERSION 0.04 (2011-09-24 za 04:24 pm) After implementing the basics, I figured out I wanted to do some things differently.
#VERSION 0.05 (2011-10-10 ma 04:27 pm) In stead of running into the minefield of threading and signals, I'll stick to the jackl & hyde setup (a sender and a listener). Went for the hping switches, moving away from the unicorn cli layout.
VERSION='0.05'

## tcpdump and tcpreplay are required in order to use sendpfast
## according to tcpreplay, some things can be done to have it send faster: http://tcpreplay.synfin.net/wiki/FAQ#Howfastcantcpreplaysendpackets

# furthermore there is currently a bug in scapy: http://trac.secdev.org/scapy/ticket/634
# You need to change /opt/local/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/site-packages/scapy/sendrecv.py:12
# The line needs to be: from utils import warning,get_temp_file,PcapReader,wrpcap
# do not forget to configure the firewall where hyde.py is running if you want to be able to do 3whandshakes. for ipfw:  deny tcp from me to any tcpflags rst


import sys, os, socket, time, random, struct, re, sets, warnings, zlib

# supress IPV6 WARNING at startup
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

# suppresses annoying message about tempnam when using sendpfast. Security issue!
warnings.filterwarnings(action="ignore",category=RuntimeWarning)

try:
    from scapy.all import *
except:
    print "install scapy"


# neat, found here: http://code.activestate.com/recipes/66517-ip-address-conversion-functions-with-the-builtin-s/
def dottedQuadToNum(ip):
    "convert decimal dotted quad string to long integer"
    return struct.unpack('!L',socket.inet_aton(ip))[0]

def numToDottedQuad(n):
    "convert long int to dotted quad string"
    return socket.inet_ntoa(struct.pack('!L',n))

# function to parse option input of the "5,7-29" format.
def optionstorange(reeks):
    outputreeks = sets.Set([])
    splitreeks = reeks.split(',')
    for r in splitreeks:
        if r.isdigit():
            outputreeks.add(int(r))
        if re.search('-', r):
            rmap = map(int, r.split('-'))
            outputreeks.update(range(rmap[0],rmap[1]+1))
    return list(outputreeks)

# Of course we could simply answer/trace any SYNACK (all the socket & sequence info is in the packets)
# But this way we can keep close track of actual responses to our stimuli, without jackL and hyde communicating about every single packet in real time.
def synjack(random32, ip, sport, dport):
    ''' Dedicated to the memory of Jack Louis
        Jan 5, 1977 - March 15, 2009

    from unicornscan:
    #define TCPHASHTRACK(output, srcip, srcport, dstport, syncookie) \
        output=(syncookie) ^ ((srcip) ^ ( ( (srcport) << 16) + (dstport) ))
    '''

    syn_jack = random32 ^ (dottedQuadToNum(ip) ^ ((sport << 16) + dport))
    return syn_jack

# in order to use synjack's we build our own TCP class.
class SYNJACK(Packet):
    name = "SYNJACK"
    fields_desc = [ ShortEnumField("sport", 20, TCP_SERVICES),
                    ShortEnumField("dport", 80, TCP_SERVICES),
                    IntField("seq", 0),
                    IntField("ack", 0),
                    BitField("dataofs", None, 4),
                    BitField("reserved", 0, 4),
                    FlagsField("flags", 0x2, 8, "FSRPAUEC"),
                    ShortField("window", 8192),
                    XShortField("chksum", None),
                    ShortField("urgptr", 0),
                    TCPOptionsField("options", {})]

    def post_build(self, p, pay):
        # synjack the packet
        global ISN
        ISN = synjack(r32, self.underlayer.dst, self.sport, self.dport)
        p = p[:4]+struct.pack("!I",ISN)+p[8:]
        # thats all folks
        p += pay
        dataofs = self.dataofs

        if dataofs is None:
            dataofs = 5+((len(self.get_field("options").i2m(self,self.options))+3)/4)
            p = p[:12]+chr((dataofs << 4) | ord(p[12])&0x0f)+p[13:]
        if self.chksum is None:
            if isinstance(self.underlayer, IP):
                if self.underlayer.len is not None:
                    ln = self.underlayer.len-20
                else:
                    ln = len(p)
                psdhdr = struct.pack("!4s4sHH",
                                     inet_aton(self.underlayer.src),
                                     inet_aton(self.underlayer.dst),
                                     self.underlayer.proto,
                                     ln)
                ck=checksum(psdhdr+p)
                p = p[:16]+struct.pack("!H", ck)+p[18:]
            elif conf.ipv6_enabled and isinstance(self.underlayer, inet6.IPv6) or isinstance(self.underlayer, inet6._IPv6ExtHdr):
                ck = inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p)
                p = p[:16]+struct.pack("!H", ck)+p[18:]
            else:
                warning("No IP underlayer to compute checksum. Leaving null.")
        return p
    def hashret(self):
        if conf.checkIPsrc:
            return struct.pack("H",self.sport ^ self.dport)+self.payload.hashret()
        else:
            return self.payload.hashret()
    def answers(self, other):
        if not isinstance(other, SYNJACK):
            return 0
        if conf.checkIPsrc:
            if not ((self.sport == other.dport) and
                    (self.dport == other.sport)):
                return 0
        if (abs(other.seq-self.ack) > 2+len(other.payload)):
            return 0
        return 1
    def mysummary(self):
        if isinstance(self.underlayer, IP):
            return self.underlayer.sprintf("SYNJACK %IP.src%:%SYNJACK.sport% > %IP.dst%:%SYNJACK.dport% %SYNJACK.flags%")
        elif conf.ipv6_enabled and isinstance(self.underlayer, inet6.IPv6):
            return self.underlayer.sprintf("SYNJACK %IPv6.src%:%SYNJACK.sport% > %IPv6.dst%:%SYNJACK.dport% %SYNJACK.flags%")
        else:
            return self.sprintf("SYNJACK %SYNJACK.sport% > %SYNJACK.dport% %SYNJACK.flags%")

# we need to unbind the default and rebind to SYNJACK
split_layers(IP, TCP)
bind_layers(IP, SYNJACK, proto=6)
# option parsing
from optparse import OptionParser

parser = OptionParser()

parser.add_option('-v', '--version',
                    action="store_true",
                    dest="SHOW_VERSION",
                    default=False,
                    help='display version')

parser.add_option("-V", "--verbose",
                    action="count",
                    dest="VERBOSE",
                    default=0,
                    help="verbose (each time more verbose so -vvvvv is really verbose)")

parser.add_option("-I", "--interface",
                    action="store",
                    dest="IFACE",
                    default=False,
                    help="interface name, like eth0 or en1, not normally required",
                    type="string")

parser.add_option('-r', '--pps',
                    action="store",
                    dest="PPS",
                    default=False,
                    type="int",
                    help='packets per second (Careful what you wish for, you just might get it!)')

parser.add_option('-a', '--spoof',
                    action="store",
                    dest="SOURCE_ADDRESS",
                    default=False,
                    type="string",
                    help='source address for packets (if hyde is running elsewhere), or \'r\' for random')

parser.add_option('-m', '--protocol',
                    action="store",
                    dest="PROTOCOL",
                    default=False,
                    type="string",
                    help='Protocol to use, can be [U]DP, [T]CP, [R]aw (IP) or I[CMP]')

parser.add_option('-S', '--synjack',
                    action='store',
                    dest='SYNJACK',
                    type='string',
                    default=False,
                    help='Set the \"synjack\" value, used to track TCP packets between jackl & hyde. Do not forget to provide the same value to hyde!')

parser.add_option('-p', '--ports',
                    action="store",
                    dest="DESTINATION_PORT",
                    default=False,
                    type="string",
                    help='global ports to scan, cannot be specified in target section')

parser.add_option("-b", "--broken-crc",
                    action="store",
                    dest="BROKEN_CRC",
                    default=False,
                    type="string",
                    help="set broken crc sums on [T]ransport layer, [N]etwork layer, or both[TN]")


parser.add_option('-s', '--baseport',
                    action="store",
                    dest="BASE_PORT",
                    default=False,
                    type="string",
                    help='set base source port, default is port 20, \'r\' for random')


parser.add_option('-f', '--TCPflags',
                    action="store",
                    dest="FLAGS",
                    default=False,
                    type="string",
                    help='TCP flags to use, can be any combination of: FSRPAUEC')

parser.add_option('-C', '--icmptype',
                    action="store",
                    dest="ICMP_TYPE",
                    default=False,
                    type="string",
                    help='ICMP type(s) to use: 0-255')

parser.add_option('-K', '--icmpcode',
                    action="store",
                    dest="ICMP_CODE",
                    default=False,
                    type="string",
                    help='ICMP Code(s) to use: 0-255')

parser.add_option('-n', '--numeric',
                    action="store_true",
                    dest="NUMERIC",
                    default=False,
                    help='numeric output')

parser.add_option('-H', '--ip-proto',
                    action='store',
                    dest='IPPROTO',
                    type='int',
                    default=False,
                    help='Set the IP protocol number, only in Raw (IP) mode')

parser.add_option('-e', '--signature',
                    action='store',
                    dest='SIGNATURE',
                    type='string',
                    default=False,
                    help='Add \'signature\', aka Raw data (in HEX: \\x08\\x00\\xf7\\xff\\x00\\x00\\x00\\x00)')

(options, args) = parser.parse_args()

try:
    options.DESTINATION_ADDRESS = args[0]
except IndexError:
    print "you need to specify an IP address (range)\nUse \"-h\" or \"--help\" for options"
    exit(1)

# options for all protocols

# set synvalue from -S, or use default
if options.SYNJACK:
    r32 = zlib.crc32(options.SYNJACK)
else:
    r32 = zlib.crc32('synjack')

ip=IP()
TIMEOUT=7

# stop resolving service names etc.
if options.NUMERIC:
    conf.noenum.add(TCP.sport, TCP.dport, UDP.sport, UDP.dport)

RANDOMSRC = False
if options.SOURCE_ADDRESS:
    if re.match('r', options.SOURCE_ADDRESS):
        RANDOMSRC = True
    else:
        ip.src = options.SOURCE_ADDRESS
#XXX this should be done differently: create an array of targets from arguments,
# probably parse them through scapy's Net function to get targets.
if options.DESTINATION_ADDRESS:
    ip.dst = str(options.DESTINATION_ADDRESS)
if options.IFACE:
    conf.iface = options.IFACE

## -V --version functionality
if options.SHOW_VERSION:
    print "%s %s" % (os.path.basename(sys.argv[0]), VERSION)
    exit(0)

## -v --verbose functionality
if options.VERBOSE > 1:
    print "using interface " + conf.iface
    print "verbosity level " + str(options.VERBOSE)

## -b, --broken-crc functionality

brokenTransportCRC = False
brokenNetworkCRC = False
if options.BROKEN_CRC:
    for option in [ c for c in options.BROKEN_CRC]:
        if option == "T":
            brokenTransportCRC = True
        elif option == "N":
            brokenNetworkCRC = True

# options options for UDP or TCP

def breakTransportCRC(pkt):
    if options.PROTOCOL == "UDP":
        TLchecksum = Ether(str(pkt))[UDP].chksum
        brokenTLchecksum = TLchecksum //2
        TLprotocol.chksum=brokenTLchecksum
        if options.VERBOSE > 2:
            print "broken %s CRC" % (options.PROTOCOL)
    elif options.PROTOCOL == "TCP":
        TLchecksum = Ether(str(pkt))[SYNJACK].chksum
        brokenTLchecksum = TLchecksum //2
        TLprotocol.chksum=brokenTLchecksum
        if options.VERBOSE > 2:
            print "broken %s CRC" % (options.PROTOCOL)

def seticmpvalues():
    if options.ICMP_TYPE:
        TLprotocol.type = optionstorange(options.ICMP_TYPE)
    if options.ICMP_CODE:
        TLprotocol.code= optionstorange(options.ICMP_CODE)

def setsocketpair():
    # create (a range of) destination port(s) to scan
    if options.DESTINATION_PORT:
        TLprotocol.dport = optionstorange(options.DESTINATION_PORT)

    # create (a range of) source port(s) to scan from
    if options.BASE_PORT:
        if re.match('r', options.BASE_PORT):
            TLprotocol.sport=RandShort()
        else:
            TLprotocol.sport = optionstorange(options.BASE_PORT)

    # support scanning from random addresses
    if RANDOMSRC:
        ip.src=RandIP("0.0.0.0/0")
    elif options.SOURCE_ADDRESS:
        ip.src=options.SOURCE_ADDRESS


# packet forging
# select protocol
if options.PROTOCOL == 'T':
    if options.FLAGS:
        TLprotocol = SYNJACK(flags=options.FLAGS)
        print TLprotocol.flags
        setsocketpair()
    else:
        TLprotocol = SYNJACK()
        setsocketpair()
elif options.PROTOCOL == 'U':
    TLprotocol = UDP()
    options.FLAGS = False
    setsocketpair()
elif options.PROTOCOL == 'I':
    TLprotocol = ICMP()
    options.FLAGS = False
    seticmpvalues()
elif options.PROTOCOL == 'R':
    print "Do not bother waiting for a response. Use wireshark."
    TIMEOUT = 1
    TLprotocol = Raw()
    if options.SIGNATURE:
        TLprotocol.load = options.SIGNATURE.replace('\\x', '').decode('hex')
    if options.IPPROTO:
        ip.proto=options.IPPROTO
    else:
        print "you need to set -H/ --ip-proto if you want to create Raw packets"
        exit(1)
else:
    TLprotocol = SYNJACK(flags=options.FLAGS)
    setsocketpair()

#if options.BASE_PORT:
#    TLprotocol.sport = options.BASE_PORT

# load all optional settings and add Ether() for sendpfast

pakket = Ether()/ip/TLprotocol
# break CRC if requested
# anything calculated we break, needs to be done after we create the "pakket"
if brokenNetworkCRC:
    NLchecksum = IP(str(pakket)).chksum
    brokenNLchecksum = NLchecksum //2
    ip.chksum=brokenNLchecksum
    if options.VERBOSE > 2:
        print "broken [N]etwork layer (IP) CRC"

if options.PROTOCOL == 'TCP' or options.PROTOCOL == 'UDP':
    if brokenTransportCRC:
        breakTransportCRC(pakket)

pakket = Ether()/ip/TLprotocol
#pakketje = Ether()/ip/TCP(dport=443)
# We want some intelligence, switching from building & sending to batch building & sending if necessary.
# to accomplish this, we would need to know how many packets are going to be build.
# We would also need to think of a way to intelligently batch, depending on the factor which creates the largest amount of packets.
print [p for p in ip.dst]
print len([p for p in ip.dst])
try:
    print len([d for d in TLprotocol.dport])
except TypeError, e:
    print "1"

try:
    print len([d for d in TLprotocol.sport])
except TypeError, e:
    print "1"
#for p in pakket:
#    p.show()
#sendpfast(pakket, pps=options.PPS, loop=1)
